上一篇我們把「傳統 HTTP 驗證方式」這個陣營講完了:Basic Auth、Digest Auth、API Key、Session-based 登入。它們的共同點是:你每次請求帶去的那個東西(帳密、API Key、Session ID),就是伺服器直接拿去比對或查表用的那件東西本尊,中間沒有再多一層選項。舉例來說:Basic Auth 就是帳密本人直接送過去比對;API Key 就是那把 Key 本身,伺服器一查就知道是哪個應用程式、專案或服務在呼叫 API;Session-based 就是那組 Session ID 本身,對應到伺服器上的一筆記錄。也因為這樣,伺服器不需要額外驗算什麼簽章,直接拿去比對或查表就能判斷要不要放行。
今天要進到另一個陣營:Token-Based Auth。這個陣營底下藏著好幾個常常被搞混的名詞:Bearer Token、JWT、Access Token、Refresh Token,不過在拆開個別名詞之前,先花一節篇幅認識這個陣營本身的共同特色,再一個一個往下看,最後跟 Session-based 登入做一次完整比較。
今天內容涵蓋:
上一節提到,傳統 HTTP 驗證方式的共同點是「你送出去的東西,本身就是伺服器要核對的憑證」——帳密、API Key、Session ID,伺服器直接比對或查表就好。
Token-Based Auth 走的是不一樣的路線:驗證用的憑證(Token)不是使用者原本就有的秘密,而是由驗證方(通常稱為授權伺服器)在驗證通過後,額外「簽發」出來的一張通行證。使用者不會每次都把帳密送出去,而是拿著這張後來拿到的 Token 去證明身份。
這樣的設計帶來幾個共同特色:
接下來就把這個陣營底下的幾個名詞——Bearer Token、JWT、Access Token、Refresh Token——一個一個拆開來看。
像是要幫產品加上「用 Google/Facebook 帳號登入」這種第三方登入,或是要設計一支同時給網頁、iOS、Android 等多個前端共用的 API,這些情境下最常用的就是 Token-Based Auth,而其中最基礎的一種規格就叫 Bearer Token。
Bearer 是英文「持有人」的意思。Bearer Token 的邏輯簡單來說:系統不管你是誰,只看你手上有沒有這張 Token——只要拿得出來,系統就當你是這張 Token 原本要給的那個人,不會再進一步確認你的真實身份。這跟現金一樣不記名:一張千元鈔票,不管原本是發給誰的,只要現在在你手上,你就能拿去花,沒有人會去查這張鈔票的「原主人」是誰。

流程如下:
Authorization header,格式是 Authorization: Bearer <token>(是不是很眼熟?上一篇 API Key 那一節也出現過這個寫法)。Bearer 開頭,取出後面的 Token 去驗證,通過才放行。範例:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
這也是為什麼上一篇 API Key 那一節會出現 Authorization: Bearer <api_key> 這種寫法——Bearer 只是一個「驗證方案(auth scheme)」的名稱,宣告「我後面帶的是一個持有人式的 Token」,它沒有規定 Token 本身要長什麼樣子,可以是隨機字串,也可以是接下來要介紹的 JWT。這個用法正式定義在 RFC 6750(OAuth 2.0 Bearer Token Usage)。
風險:Bearer Token 就跟現金一樣,誰拿到就是誰的,伺服器不會額外驗證「這個 Token 本來是不是發給你的」。一旦 Token 被攔截或洩漏,攻擊者不需要密碼,直接拿著 Token 就能冒充使用者,直到過期為止。所以傳輸 Bearer Token 一定要搭配 HTTPS,並讓有效期盡量短。
上面提到 Bearer Token 可以是任何格式的字串,而 JWT(JSON Web Token,定義在 RFC 7519) 是目前最常拿來當 Bearer Token 內容的一種格式。
跟前面提到的 API Key 最大的差異在於:API Key 只是一串隨機字串,本身不帶任何資訊,伺服器要知道這把 Key 屬於誰都得查資料庫;而 JWT 是把身份資訊直接編碼進 Token 本身,伺服器驗證簽名就能相信裡面的內容,不用每次都查資料庫。
JWT 由三段組成,用 . 分隔:
Header.Payload.Signature
Header(標頭):可以想成 JWT 的外包裝標籤,常見寫法是 typ: JWT,表示「這是一顆 JWT」;alg 則表示「它用哪一種演算法簽名」,例如 HS256 或 RS256。
Payload(內容):放實際要傳遞的資料,稱為 Claims(聲明)。常見的內建欄位有:
iss(Issuer):簽發者sub(Subject):這個 Token 代表的對象,通常是使用者 IDaud(Audience):這個 Token 是要給誰用的exp(Expiration Time):過期時間iat(Issued At):簽發時間jti(JWT ID):這個 Token 的唯一編號,可以用來防止同一個 Token 被攔截後拿去重複使用(重放攻擊)除了這些內建欄位,也可以自訂欄位,例如使用者角色、權限等。
Signature(簽名):把 Header、Payload 加上一把只有伺服器知道的密鑰,一起算出來的簽名,用來確保內容沒有被竄改。

流程如下:
Authorization: Bearer <JWT> 裡面。實務上驗證 JWT,可以先拆成兩層來看。
第一層是驗證簽名。伺服器會用密鑰或公鑰重新驗算 JWT 的簽名,確認 Header 和 Payload 沒有被改過。這一步只能證明「這顆 Token 的內容沒有被竄改」。
第二層是檢查內容,也就是檢查 Payload 裡的 Claims 是否合理。例如:
exp:Token 是否已經過期。iss:是不是由可信任的簽發者發出。aud:這顆 Token 是不是發給目前這支 API 用的。nbf:如果有設定,現在是不是已經可以使用。alg:簽章演算法是不是伺服器允許的類型。也就是說,API Server 會根據這些檢查結果,判斷這顆 Token 能不能被信任。簽名用來確認內容沒有被改過;Claims 則用來確認這顆 Token 是否仍在有效期限內、是否來自可信任的簽發者,並且是否適用於目前這支 API。
接著再看簽名本身。JWT 常見的簽章方式可以先分成兩種:
| 類型 | 常見演算法 | 簽署方式 | 適合情境 |
|---|---|---|---|
| 對稱式簽章 | HS256 |
同一把 secret 負責簽署與驗證 | 同一個後端系統自己簽、自己驗 |
| 非對稱式簽章 | RS256、ES256 |
私鑰簽署,公鑰驗證 | 多個服務都需要驗證同一個 Token |
對稱式簽章的重點是:誰拿到 secret,誰就能簽出新的 Token。因此它比較適合同一個後端系統內部使用,不適合把 secret 放在瀏覽器或手機 App 這種無法安全保管秘密的環境。
非對稱式簽章則是把「簽署」和「驗證」拆開。簽發者保管私鑰,用私鑰簽 Token;其他 API 服務只需要拿公鑰驗證簽名,不需要知道私鑰。這也是為什麼跨服務、多團隊的系統,通常會偏向使用 RS256 或 ES256。
💡 伺服器驗證 JWT 時,不應該完全相信 Token Header 裡的
alg欄位。比較安全的做法是:伺服器端先明確規定「允許哪些簽章演算法」,再依照這份允許清單驗證 Token。
否則攻擊者可能利用演算法混淆(Algorithm Confusion)等問題,誘使伺服器使用錯誤的驗證方式,進而接受偽造的 Token。
JWT 的常見風險:
exp)之前,伺服器很難主動讓它失效。Session 可以直接在伺服器端刪掉一筆記錄,但 JWT 驗證時通常不查資料庫;除非額外實作黑名單或撤銷機制,否則「強制登出」會比較麻煩。alg 決定怎麼驗證,就可能被攻擊者利用。這也是前面提醒「不要完全相信 alg」的原因。Token 存放在哪裡?
理解 JWT 的格式和驗證方式之後,下一個問題是:客戶端要把 Token 放在哪裡?
| 儲存位置 | 主要風險 | 說明 |
|---|---|---|
localStorage / sessionStorage |
XSS 竊取 | 頁面只要被注入惡意 JavaScript,就能直接讀走 Token,sessionStorage 差別只在分頁關閉會清掉 |
| JavaScript 記憶體變數 | 重整頁面會消失 | 比存進 localStorage 安全一些,代價是重整頁面要重新登入 |
HttpOnly Cookie |
CSRF | JavaScript 讀不到,但瀏覽器會自動帶上,要另外做 CSRF 防護 |
| Backend-for-Frontend(後端代存) | 需要額外的伺服器端狀態 | 瀏覽器只拿到一個 Session Cookie,真正的 Token 由後端保管,是目前公認對瀏覽器應用比較安全的做法 |
把 JWT 直接放進 localStorage 很方便,但網站只要有 XSS 漏洞,Token 就可能被整個偷走。所以不少團隊會改用 HttpOnly Cookie,或乾脆讓後端幫忙保管 Token,瀏覽器只留一個安全的 Session Cookie。
不管是單純的 Bearer Token 還是 JWT,都會遇到同一個兩難:Token 的有效期要設多長?
設太長,一旦洩漏攻擊者能用很久,風險高;設太短,使用者動不動就要重新登入輸入密碼,體驗很差。
解法是把 Token 拆成兩種,各司其職:
| 項目 | Access Token | Refresh Token |
|---|---|---|
| 用途 | 真正拿去存取資源的憑證,放在每次 API 請求裡 | 只用來換一張新的 Access Token,本身不能直接存取資源 |
| 有效期 | 短,通常 15 分鐘〜1 小時 | 長,通常 7〜30 天 |
| 洩漏後的影響 | 影響有限,很快就過期 | 影響較大,攻擊者可以一直換到新的 Access Token |
儲存建議(最佳實踐):Refresh Token 效期長、一旦洩漏影響範圍大,建議存放在 httpOnly Cookie,不建議像真正拿去存取資源的 Access Token 一樣直接放進 localStorage,降低它被 XSS 直接偷走的風險。

流程如下:
這套設計正式定義在 RFC 6749(OAuth 2.0 Authorization Framework) 裡,Refresh Token 被設計成只能拿去跟「授權伺服器」換新 Token,不會被送到真正提供資源的伺服器,降低洩漏時被直接拿去用的風險。也就是說,Access Token 是拿去敲資源伺服器的門,Refresh Token 則是拿回授權伺服器換新通行證的憑證,兩者不應該混用。
風險:Refresh Token 有效期長、權限等同於「重新登入一次」,一旦洩漏,攻擊者能持續換到新的 Access Token,而且這種濫用模式跟正常使用者行為很像,比 Access Token 洩漏更難被偵測到。目前業界的做法是Refresh Token Rotation:每次拿 Refresh Token 換新 Token 時,舊的 Refresh Token 就立刻失效、換發一張新的,一旦偵測到「已經失效的 Refresh Token」被拿來使用,就代表可能已經洩漏,能立刻撤銷整組 Token。除了 Refresh Token Rotation,常見的撤銷手段還有下面這兩種,簡单說就是「自己查一本名單」跟「直接去問發 Token 的人」:
這兩種做法的代價都是:每次驗證都得額外查一次(查名單或問伺服器),也就是把 JWT 本來「不用查資料庫、只驗簽名」的快,拿一部分回來換「能隨時強制失效」的能力,屬於效能與安全性之間的取捨。
💡 順帶一提,Access Token 不一定要長得像 JWT。
JWT 屬於 Self-contained Token:Token 本身就可以攜帶使用者、權限、過期時間等資訊,Resource Server 驗證簽章與相關 Claim 後,就能直接判斷 Token 的內容與有效性。
相對地,Opaque Token(不透明 Token,也常被稱為 Reference Token) 通常只是一串隨機、唯一,而且對客戶端本身沒有意義的字串。
它不會把授權資訊直接放在 Token 裡,而是把 Token 當成一把「查詢鑰匙」。Resource Server 必須查詢資料庫、快取,或透過 Token Introspection 向 Authorization Server 查詢,才能知道這顆 Token 是否有效、代表誰,以及擁有哪些權限。
所以簡單來說:JWT 是把資訊放在 Token 裡;Opaque Token 則是把資訊留在伺服器端,Token 本身主要負責當索引。
OAuth 2.0 本身並沒有規定 Access Token 一定要採用哪一種格式。實際要使用 JWT 還是 Opaque Token,取決於 Authorization Server 的設計。
到這裡,Token-Based Auth 陣營底下的幾個名詞都拆開看過了。這裡特別挑 Session-based 登入出來比較,而不是整個「傳統 HTTP 驗證方式」陣營,是因為 Basic Auth、Digest Auth、API Key 本質上都是「每次請求重新驗證一次」,並沒有「在伺服器端維持登入狀態」這件事。只有 Session-based 登入才會面對這個問題,而且它跟 Token-Based Auth 剛好給出相反的答案——這就是為什麼下面挑 Session-based 登入來做完整比較:
| 比較項目 | Session-based 登入 | Token-Based Auth(以 JWT 為例) |
|---|---|---|
| 狀態放在哪 | 伺服器端(Session) | 客戶端自己帶著(Token 本身) |
| 伺服器要不要查資料庫 | 要,靠 Session ID 查記錄 | 不用,驗證簽名就能相信內容 |
| 強制登出 | 容易,把伺服器上的 Session 刪掉就好,立即生效 | 比較困難,Token 到期前很難主動撤銷 |
| 擴展到多台伺服器 | 需要額外設計,讓每台伺服器都查得到同一份 Session | 天生好擴展,任何一台伺服器都能獨立驗證 |
| CSRF/XSS 風險 | Cookie 搭配 HttpOnly、Secure、SameSite 可以有效防 XSS 直接讀取,但要另外處理 CSRF | 存放方式沒選好就容易出事:放 localStorage 容易被 XSS 偷走,放 HttpOnly Cookie 又要處理 CSRF |
| 適合場景 | 同一個網站、後端可控的系統 | 跨服務、跨網域、第三方串接 |
兩者的差別像是:Session-based 登入是「先辦一張會員卡,之後只要出示卡號,店員再自己回後台查這張卡對應的是誰」;Token-Based Auth 則是「每次都自己帶著一份寫好身份資訊、還蓋上店家簽名的證明文件,店員只要驗證那個簽名是不是真的,就能直接相信文件內容,不用再回頭查資料」。兩者沒有絕對的優劣,實務上很多系統甚至會混用——例如網頁前端用 Session,對外開放的 API 用 JWT。
| 概念 | 說明 |
|---|---|
| Bearer Token | 一種驗證方案,規定怎麼帶著 Token 送出去;像持有人式的通行證,誰拿著就代表是誰,內容格式不限 |
| JWT | Token 的一種格式,把身份資訊編碼進 Token 本身,伺服器驗證簽名就能相信內容 |
| Access Token | 短效,真正拿去存取資源 |
| Refresh Token | 長效,用來換新的 Access Token |
今天把 Token-Based Auth 底下的 Bearer、JWT、Access Token、Refresh Token 都拆開講完了,也跟 Session-based 登入做了完整比較。到目前為止,我們已經拼出了單一系統要怎麼驗證身份、怎麼維持登入狀態的完整拼圖。但如果使用者要用同一個帳號登入好幾個不同的系統呢?這就是 SSO(單一登入) 要解決的問題,內容也不少,留到下一篇專門來談。